/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.rh.game.impl;

import com.rh.game.Chapter;
import com.rh.game.Character;
import com.rh.game.ChatWithConditions;
import com.rh.game.EquipmentChange;
import com.rh.game.EventsAndChat;
import com.rh.game.GameClothing;
import com.rh.game.GameCondition;
import com.rh.game.GameEffect;
import com.rh.game.GameObject;
import com.rh.game.Location;
import com.rh.game.Tile;
import com.rh.game.MagicalEffect;
import com.rh.game.World;

import com.rh.game.loader.Portal;

import com.rh.gui.RenderEvent;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Hayden
 */
public class World1 implements World {
    private static void debug(Object o)  {
        // System.out.println("World1: " + o);
    }
    
    private Map<String, MapGrid> maps = new HashMap<String, MapGrid>();
    private MapGrid mapGrid = MapGrid.NULL;
    
    private void portal(Character pCharacter, Portal pPortal)  {
        debug("PORTAL!!!");
        pCharacter.setLocation(pPortal.getTo());
        mapGrid = maps.get(pPortal.getTo().getID());
        mapGrid.update(mapGameObjects.values());
    }
    
    public byte[][] getGrid()  {
        return mapGrid.getGrid();
    }
    byte[][] getBackgroundGrid()  {
        return mapGrid.getBackgroundGrid();
    }
    
    private String currentChapterID = "";
    public String getCurrentChapterID()  {
        return currentChapterID;
    }
    
    private Character player = null;
    public Character getPlayer()  { return player; }
    
    private List<Chapter> chapters = new ArrayList<Chapter>();
    
    private Map<String, GameObject> mapGameObjects = new HashMap<String, GameObject>();
    
    Character getCharacter(String pCharacterID)  {
        if (getPlayer().getID().equals(pCharacterID))  return getPlayer();        
        GameObject go = mapGameObjects.get(pCharacterID);
        if (go instanceof Character)  return (Character) go;
        return Character.NULL;
    }
    
    GameObject getGameObject(String pID)  {
        GameObject go = mapGameObjects.get(pID);
        if (go != null)  return go;
        return GameObject.NULL;
    }
    
    private List<Tile> tiles = new ArrayList<Tile>();
    public List<Tile> getTiles()  {
        return tiles;
    }
    
    public List<GameObject> getGameObjectsAroundPlayer()  {
        List<GameObject> objects = new ArrayList<GameObject>();
        for (Map.Entry<String, GameObject> entry : mapGameObjects.entrySet())  {
            GameObject go = entry.getValue();
            if (go == getPlayer())  continue;
            if (go.getLocation() == getPlayer().getLocation())  {
                objects.add(go);
            }
        }
        return objects;
    }
    
    List<RenderEvent> intro(Character pCharacter)  {
        debug("Intro for " + pCharacter);
        List<RenderEvent> events = new ArrayList<RenderEvent>();

        Chapter c0 = null;
        if (getCurrentChapterID().equals(""))  {
            c0 = chapters.get(0);
            currentChapterID = c0.getID();            
        }
        else  {
            for (Chapter c : chapters)  {
                if (c.getID().equals(getCurrentChapterID()))  {
                    c0 = c;
                    break;
                }
            }
        }
        
        for (ChatWithConditions c : c0.getChats())  {
            debug(c);
            if (!c.isIntro())  continue;
            if (c.getSpeaker() != pCharacter)  continue;
            
            boolean allMet = true;
            for (GameCondition gc : c.getConditions())  {
                allMet = allMet && gc.evaluate();
            }
            if (allMet)  {
                events.add(RenderEvent.chat(c.getID()));
                break;
            }
        }
        
        return events;
    }
    
    public List<RenderEvent> equipment(Character whom, EquipmentChange change)  {
        List<RenderEvent> events = new ArrayList<RenderEvent>();
        
        // Take off items
        for (GameClothing gc : change.getDisrobed())  {
            whom.getWearing().remove(gc);
            whom.addInventory(gc);
        }
        
        // Put on items
        for (GameClothing gc : change.getPutOn())  {
            whom.getWearing().add(gc);
            whom.removeInventory(gc);
            
            for (MagicalEffect me : gc.getEffects())  {
                if (me.getWhen() == MagicalEffect.When.PUT_ON)  {
                    if (me.getCondition().evaluate(whom))  {
                        for (GameEffect ge : me.getEffects())  {
                            ge = GameEffect1.wearer(ge, whom);
                            debug("Put On: " + ge.getCharacter());
                            events.addAll(this.gameEffect(ge, whom));                            
                        }
                    }
                }
            }
        }

        if (events.size() > 0)  return events;
        return RenderEvent.EMPTY_LIST;
    }
    
    public List<RenderEvent> gameEffect(GameEffect effect)  {
        return gameEffect(effect, Character.NARRATOR);
    }

    private List<RenderEvent> gameEffect(GameEffect effect, Character whom)  {
        List<RenderEvent> events = new ArrayList<RenderEvent>();
        debug("world is executing: " + effect);
        
        if (effect.getType() == GameEffect.Type.INVENTORY_ADD)  {
            effect.getCharacter().addInventory(effect.getObject());
        }
        else if (effect.getType() == GameEffect.Type.INVENTORY_REMOVE)  {
            effect.getCharacter().removeInventory(effect.getObject());
        }
        else if (effect.getType() == GameEffect.Type.WEARING_REMOVE)  {
            effect.getCharacter().getWearing().remove(effect.getObject());
        }
        else if (effect.getType() == GameEffect.Type.FEMALE_TO_MALE)  {
            effect.getCharacter().setGender(Character.Gender.MALE);
            getGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()] = effect.getCharacter().getGridID();
        }
        else if (effect.getType() == GameEffect.Type.FEMALE_TO_MALE)  {
            effect.getCharacter().setGender(Character.Gender.MALE);
            events.add(RenderEvent.femaleToMale(effect.getCharacter()));
            getGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()] = effect.getCharacter().getGridID();
        }
        else if (effect.getType() == GameEffect.Type.MALE_TO_FEMALE)  {
            effect.getCharacter().setGender(Character.Gender.FEMALE);
            events.add(RenderEvent.maleToFemale(effect.getCharacter()));
            getGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()] = effect.getCharacter().getGridID();
        }
        else if (effect.getType() == GameEffect.Type.PLAYER_STEP)  {
            boolean mayMove = true;
            
            int y = effect.getLocation().getY();
            int x = effect.getLocation().getX();
            
            if ((y < 0) || (y >= this.getGrid().length))  {
                debug("new y is invalid " + y);
                mayMove = false;
            }
            if (mayMove)  {
                if ((x < 0) || (x >= this.getGrid()[y].length))  {
                    debug("new x is invalid " + x);
                    mayMove = false;
                }
            }
            
            if (mayMove)  {
                byte b = getGrid()[y][x];
                Tile t = getTiles().get(b);
                if (t.isWalkable())  {
                    getGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()] = getBackgroundGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()];
                    effect.getCharacter().setLocation(effect.getLocation());
                    getGrid()[effect.getLocation().getY()][effect.getLocation().getX()] = player.getGridID();
                }
                else  {
                    debug("can not move because " + effect + " is occupied by Tile #" + b);
                    mayMove = false;
                }
            }

            // Did they take a portal???
            if (mayMove)  {
                for (Portal p : mapGrid.getPortals())  {
                    if (p.getFrom().equals(effect.getCharacter().getLocation()))  {
                        portal(effect.getCharacter(), p);
                        break;
                    }
                }
            }
        }
        else if (effect.getType() == GameEffect.Type.MOVE_CHARACTER)  {
            if (effect.getCharacter().getLocation() != Location.NULL)  {
                if (effect.getCharacter().getLocation().getID().equals(mapGrid.getZoneID()))  {
                    getGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()] = this.getBackgroundGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()] = getBackgroundGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()];
                }
            }
            effect.getCharacter().setLocation(effect.getLocation());
            if (effect.getLocation() != Location.NULL)  {
                if (effect.getCharacter().getLocation().getID().equals(mapGrid.getZoneID()))  {
                    getGrid()[effect.getLocation().getY()][effect.getLocation().getX()] = effect.getCharacter().getGridID();
                }
            }
        }
        else if (effect.getType() == GameEffect.Type.GENDER_SWAP)  {
            Character.Gender g1 = effect.getCharacter().getGender();
            Character.Gender g2 = effect.getCharacter2().getGender();
            
            effect.getCharacter().setGender(g2);
            getGrid()[effect.getCharacter().getLocation().getY()][effect.getCharacter().getLocation().getX()] = effect.getCharacter().getGridID();

            effect.getCharacter2().setGender(g1);
            getGrid()[effect.getCharacter2().getLocation().getY()][effect.getCharacter2().getLocation().getX()] = effect.getCharacter2().getGridID();
            
            events.add(RenderEvent.genderSwap(effect.getCharacter(), effect.getCharacter2()));
            
        }
        else if (effect.getType() == GameEffect.Type.GAME_OVER)  {
            events.add(RenderEvent.GAME_OVER);
        }
        
        if (events.size() > 0)  return events;
        return RenderEvent.EMPTY_LIST;
    }
    
    public EventsAndChat getChat(String chatID)  {
        List<RenderEvent> events = new ArrayList<RenderEvent>();
        
        // This is a really bad algorithm
        for (Chapter p : chapters)  {
            if (!p.getID().equals(getCurrentChapterID()))  continue;
            
            for (ChatWithConditions cwc : p.getChats())  {
                if (cwc.getID().equals(chatID))  {
                    for (GameEffect ge : cwc.getEffects())  {
                        events.addAll(this.gameEffect(ge));
                    }
                    return new EventsAndChat(events, cwc);
                }
            }
        }
        
        return EventsAndChat.NULL;
    }
    
    public List<RenderEvent> processInput(String input)  {
        debug(input);
        if (getCurrentChapterID().equals(""))  {
            return intro(Character.NARRATOR);
        }
        
        if (input == null)  return RenderEvent.EMPTY_LIST;
        input = input.trim();
        String[] tokens = input.split(" ");
        if (tokens.length == 0)  return RenderEvent.EMPTY_LIST;
        
        List<RenderEvent> events = new ArrayList<RenderEvent>();
        
        if (tokens.length == 1)  {
            String action = tokens[0].toUpperCase();
            if (action.equals("CHAT"))  {
                for (GameObject go : mapGameObjects.values())  {
                    debug(go + ", " + go.getLocation());
                    if (go == getPlayer())  continue;
                    if (go instanceof Character)  {
                        int dx = Math.abs(getPlayer().getLocation().getX() - go.getLocation().getX());
                        debug(go + ", dx := " + dx);
                        if (dx > 1)  continue;
                        
                        int dy = Math.abs(getPlayer().getLocation().getY() - go.getLocation().getY());
                        debug(go + ", dy := " + dy);
                        if (dy > 1)  continue;
                        
                        Character c = (Character) go;
                        events.addAll(intro(c));
                        break;
                    }
                }
            }            
        }
        else if (tokens.length == 2)  {
            String action = tokens[0].toUpperCase();
            if (action.equals("TALK"))  {
                String to = tokens[1];
                boolean ctt = canTalkWith(to);
                if (ctt)  {
                    // Now we need to find the first thing they can say
                    Character c = (Character) mapGameObjects.get(to);
                    events.addAll(intro(c));
                }
                else  {
                    debug("Can not talk to: " + to);
                }
            }
            else if (action.equals("MOVE"))  {
                String dir = tokens[1].toUpperCase();
                GameEffect ge1 = GameEffect1.move(getPlayer(), dir);
                events.addAll(this.gameEffect(ge1));
            }
        }
        
        if (events.size() > 0)  return events;
        return RenderEvent.EMPTY_LIST;
    }

    public World1(Character pPlayer, List<Character> pCharacters, List<GameObject> pObjects, List<Chapter> pChapters, Collection<MapGrid> pMapGrids, List<Tile> pTiles)  {
        super();
        
        debug("create world");
        chapters.addAll(pChapters);

        for (MapGrid mg : pMapGrids)  {
            maps.put(mg.getZoneID(), mg);
        }
        mapGrid = maps.get(pPlayer.getLocation().getID());
        tiles = pTiles;
        
        player = pPlayer;
        mapGameObjects.put(player.getID(), player);
        
        for (GameObject c : pObjects)  {
            mapGameObjects.put(c.getID(), c);            
        }
        for (Character c : pCharacters)  {
            mapGameObjects.put(c.getID(), c);            
        }
        
        mapGrid.update(mapGameObjects.values());
    }
    
    boolean canTalkWith(String pCharacterID)  {
        GameObject go = mapGameObjects.get(pCharacterID);
        debug("canTalkWith: " + pCharacterID + ", " + go);
        if (go == null)  return false;
        if (!(go instanceof Character))  return false;
        debug("Character Location: " + go.getLocation() + ", Player Location: " + player.getLocation());
        
        if (player.getLocation().getID().equals(Location.NULL.getID()))  return false;
        
        if (!player.getLocation().getID().equals(go.getLocation().getID()))  return false;
        
        int dx = Math.abs(getPlayer().getLocation().getX() - go.getLocation().getX());
        if (dx > 1)  return false;;
        int dy = Math.abs(getPlayer().getLocation().getY() - go.getLocation().getY());
        if (dy > 1)  return false;
        
        return true;
    }
}
